﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

namespace GroundStation
{
    class Map
    {
        // data structures
        private struct Coord
        {
            public float x;
            public float y;
        };

        private struct Name
        {
            public Coord coord;
            public string text;
        };

        private struct Way
        {
            public byte type;
            public List<Coord> refs;
        };

        List<Way> highway = new List<Way>();
        List<Way> railway = new List<Way>();
        List<Way> forest = new List<Way>();
        List<Way> waterSurface = new List<Way>();
        List<Way> stream = new List<Way>();
        List<Way> residential = new List<Way>();
        List<Name> names = new List<Name>();

        uint maxIdX = 0;  // map y segment count
        uint maxIdY = 0;  // map x segment count
        float maxX = 0;   // map max x
        float maxY = 0;   // map max y
        float minX = 0;   // map min x
        float minY = 0;   // map min y
        uint partX = 0;   // segment width
        uint partY = 0;   // segment height
        uint zoom = 0;    // zoom
        uint actX = 0;    // actual segment id x
        uint actY = 0;    // actual segment id y
        uint actMinX = 0; // actual min x segment id
        uint actMaxY = 0; // actual max y segment id

        float posX = 0;   // actual map position x
        float posY = 0;   // actual map position y
        float lastPosX = 0;
        float lastPosY = 0;
        float offsetPosX = 0;
        float offsetPosY = 0;
        int offsetX = 0;
        int offsetY = 0;

        bool change = false;
        bool zoomed = false;

        public bool Change
        {
            get
            {
                return zoomed;
            }
            set
            {
                zoomed = value;
            }
        }

        

        // offset for actual position
        double crossOffsetX = 0, crossOffsetY = 0;

        //Coord lastTrackCoord = new Coord();
        List<Coord> track = new List<Coord>();

        Coord groundStationPosition = new Coord();
        bool groundStationPositionFixed = false;

        BufferedGraphicsContext context = BufferedGraphicsManager.Current;
        BufferedGraphics buffer = null;
        Bitmap bmp;
        Graphics gBmp;
        Graphics gOut;

        Pen redPen = new Pen(Color.Red, 2);
        Pen blackPen = new Pen(Color.Magenta, 2);
        Pen whitePen = new Pen(Color.Honeydew, 2);
        Pen grayPen = new Pen(Color.DarkSlateGray, 2);
        Pen bluePen = new Pen(Color.Blue, 2);
        Pen brownPen = new Pen(Color.Brown, 2);
        Pen yellowPenBold = new Pen(Color.Yellow, 2);
        Pen redPenBold = new Pen(Color.Red, 3);
        Pen pinkPenBold = new Pen(Color.Tomato, 4);
        Pen violetPenBold = new Pen(Color.HotPink, 5);
        Pen greenPen = new Pen(Color.GreenYellow, 2);

        SolidBrush blackBrush = new SolidBrush(Color.Black);
        SolidBrush blueBrush = new SolidBrush(Color.LightSeaGreen);
        SolidBrush greenBrush = new SolidBrush(Color.LimeGreen);
        SolidBrush grayBrush = new SolidBrush(Color.Gray);
        SolidBrush purpleBrush = new SolidBrush(Color.Purple);

        Font font = new Font("Arial", 8);

        public string MapFolder { get; set; }
        /*//{        get
            {
                return MapFolder;
            }
            /*set
            {
                MapFolder = value;
            }*/
        //}

        public Map(string defMapFolder)
        {
            MapFolder = defMapFolder; // +Path.DirectorySeparatorChar + "..";
        }
        public Map()
        {
            MapFolder = Path.GetDirectoryName(Application.ExecutablePath) + Path.DirectorySeparatorChar + "..";
        }

        //GpsReceiver gps = new GpsReceiver();

        /// <summary>
        /// Map zoom in
        /// </summary>
        public void ZoomIn()
        {
            uint newzoom = zoom;
            switch (zoom)
            {
                case 50:
                    newzoom = 30;
                    break;
                case 30:
                    newzoom = 20;
                    break;
                case 20:
                    newzoom = 15;
                    break;
                case 15:
                    newzoom = 10;
                    break;
                case 10:
                    newzoom = 5;
                    break;
                case 5:
                    newzoom = 2;
                    break;
            }
            crossOffsetX = crossOffsetX * (zoom - newzoom);
            crossOffsetY = crossOffsetX * (zoom - newzoom);
            zoom = newzoom;
            /*
            switch (zoom)
            {
                case 50:
                    newzoom = 30;
                    crossOffsetX = crossOffsetX*(zoom-newzoom);
                    crossOffsetY = crossOffsetX * (zoom - newzoom);
                    zoom = 30;
                    break;
                case 30:
                    zoom = 20;
                    break;
                case 20:
                    zoom = 15;
                    break;
                case 15:
                    zoom = 10;
                    break;
                case 10:
                    zoom = 5;
                    break;
                case 5:
                    zoom = 2;
                    break;
            }
            //*/
            ReadMapConfig(zoom);
            zoomed = true;
        }

        /// <summary>
        /// Map zoom out
        /// </summary>
        public void ZoomOut()
        {
            uint newzoom = zoom;
            switch (zoom)
            {
                case 30:
                    newzoom = 50;
                    break;
                case 20:
                    newzoom = 30;
                    break;
                case 15:
                    newzoom = 20;
                    break;
                case 10:
                    newzoom = 15;
                    break;
                case 5:
                    newzoom = 10;
                    break;
                case 2:
                    newzoom = 5;
                    break;
            }
            crossOffsetX = crossOffsetX * (newzoom - zoom);
            crossOffsetY = crossOffsetX * (newzoom - zoom);
            zoom = newzoom;
            /*
            switch (zoom)
            {
                case 30:
                    zoom = 50;
                    break;
                case 20:
                    zoom = 30;
                    break;
                case 15:
                    zoom = 20;
                    break;
                case 10:
                    zoom = 15;
                    break;
                case 5:
                    zoom = 10;
                    break;
                case 2:
                    zoom = 5;
                    break;
            }
            //*/
            ReadMapConfig(zoom);
            zoomed = true;
        }

        /// <summary>
        /// Initializes 
        /// </summary>
        /// <param name="picturebox">Picturebox.</param>
        public void Init(PictureBox pictureBox)
        {
            buffer = context.Allocate(pictureBox.CreateGraphics(), pictureBox.DisplayRectangle);
            buffer.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;//.HighSpeed; // speed or quality (antialiasing)
            gOut = buffer.Graphics;

            bmp = new Bitmap(2100, 2100);
            gBmp = Graphics.FromImage(bmp);
            gBmp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //.HighSpeed; // speed or quality (antializasing)

            zoom = 20;
            posX = 1600452.625f;
            posY = 6090545.0f;
            ReadMapConfig(zoom);

            //gps.Init("COM3");
        }

        /// <summary>
        /// Stops gps
        /// </summary>
        public void Stop()
        {
            //gps.Stop();
        }

        /// <summary>
        /// Reads config file of segment
        /// </summary>
        /// <param name="zoom">Zoom.</param>
        private void ReadMapConfig(uint zoom)
        {
            string fileName = "";
            string line = "";
            fileName = MapFolder + Path.DirectorySeparatorChar + "map" + Path.DirectorySeparatorChar + zoom.ToString() + Path.DirectorySeparatorChar + "config.ini";
            try
            {
                using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    using (StreamReader sr = new StreamReader(fs))
                    {
                        while ((line = sr.ReadLine()) != null)
                        {
                            string[] fields = line.Split('=');// Regex.Split(line, "=");

                            if (fields.Length == 2)
                            {
                                if (fields[0] == "max_id_x") maxIdX = uint.Parse(fields[1]);
                                if (fields[0] == "max_id_y") maxIdY = uint.Parse(fields[1]);
                                if (fields[0] == "max_x") maxX = float.Parse(fields[1], System.Globalization.CultureInfo.InvariantCulture);
                                if (fields[0] == "max_y") maxY = float.Parse(fields[1], System.Globalization.CultureInfo.InvariantCulture);
                                if (fields[0] == "min_x") minX = float.Parse(fields[1], System.Globalization.CultureInfo.InvariantCulture);
                                if (fields[0] == "min_y") minY = float.Parse(fields[1], System.Globalization.CultureInfo.InvariantCulture);
                                if (fields[0] == "part_x") partX = uint.Parse(fields[1]);
                                if (fields[0] == "part_y") partY = uint.Parse(fields[1]);
                            }
                        }
                    }
                }
            }
            catch (Exception)
            { }
        }

        /// <summary>
        /// Loads segment
        /// </summary>
        /// <param name="zoom">Zoom.</param>
        /// <param name="x">X id of segment.</param>
        /// /// <param name="y">Y id of segment.</param>
        private void LoadSegment(uint zoom, uint x, uint y)
        {
            string fileName = "";
            ushort length = 0;

            // objects
            fileName = MapFolder + Path.DirectorySeparatorChar + "map" + Path.DirectorySeparatorChar + zoom.ToString() + Path.DirectorySeparatorChar + "objects" + Path.DirectorySeparatorChar + x.ToString("0000") + y.ToString("0000") + ".obj";
            if (File.Exists(fileName))
            {
                using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    using (BinaryReader br = new BinaryReader(fs))
                    {
                        int pos = 0;
                        int brlength = (int)br.BaseStream.Length;
                        while (pos < brlength)
                        {
                            Way newWay = new Way();
                            newWay.refs = new List<Coord>();
                            try
                            {
                                newWay.type = br.ReadByte();
                                pos += sizeof(byte);
                                length = br.ReadUInt16();
                                pos += sizeof(UInt16);
                                for (uint j = 0; j < length; j++)
                                {
                                    Coord newCoord = new Coord();
                                    newCoord.x = br.ReadSingle();
                                    pos += sizeof(float);
                                    newCoord.y = br.ReadSingle();
                                    pos += sizeof(float);
                                    newWay.refs.Add(newCoord);
                                }

                                switch (newWay.type)
                                {
                                    case 0:
                                    case 1:
                                    case 2:
                                    case 3:
                                    case 4:
                                    case 5:
                                    case 6:
                                    case 7:
                                        highway.Add(newWay);
                                        break;

                                    case 8:
                                        railway.Add(newWay);
                                        break;

                                    case 9:
                                        forest.Add(newWay);
                                        break;

                                    case 10:
                                    case 11:
                                    case 14:
                                        waterSurface.Add(newWay);
                                        break;

                                    case 12:
                                    case 13:
                                        stream.Add(newWay);
                                        break;

                                    case 15:
                                        residential.Add(newWay);
                                        break;

                                    default:
                                        break;
                                }
                            }
                            catch (Exception)
                            {
                            }
                        }
                    }
                }
            }

            // labels
            fileName = MapFolder + Path.DirectorySeparatorChar + "map" + Path.DirectorySeparatorChar + zoom.ToString() + Path.DirectorySeparatorChar + "labels" + Path.DirectorySeparatorChar + x.ToString("0000") + y.ToString("0000") + ".lbl";
            if (File.Exists(fileName))
            {
                using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    using (BinaryReader br = new BinaryReader(fs))
                    {
                        int pos = 0;
                        int brlength = (int)br.BaseStream.Length;
                        while (pos < brlength)
                        {
                            Name newName = new Name();
                            newName.coord = new Coord();
                            try
                            {
                                newName.coord.x = br.ReadSingle();
                                pos += sizeof(float);
                                newName.coord.y = br.ReadSingle();
                                pos += sizeof(float);
                                length = br.ReadUInt16();
                                pos += sizeof(UInt16);
                                byte[] buf = new byte[length];
                                for (int i = 0; i < length; i++)
                                {
                                    buf[i] = br.ReadByte();
                                    pos += sizeof(byte);
                                }
                                Encoding enc = System.Text.Encoding.GetEncoding("utf-8");
                                newName.text = enc.GetString(buf);
                                if (!newName.text.Contains("?"))
                                {
                                    names.Add(newName);
                                }
                            }
                            catch (Exception)
                            {
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Adds offset on mouse drag
        /// </summary>
        /// <param name="x">Offset X.</param>
        /// /// <param name="y">Offset Y.</param>

        public void AddOffset(long x, long y)
        {
            //offsetPosX -= x; // zoom;
            //offsetPosY -= y; // zoom;
            if (groundStationPositionFixed)
            {
                offsetPosX += x * zoom;
                offsetPosY -= y * zoom;
            }
            else
            {
                posX += x * zoom;
                posY -= y * zoom;
            }
            crossOffsetX -= x; // zoom;
            crossOffsetY -= y;// zoom;
        }

        /// <summary>
        /// Sets gps fix position
        /// </summary>
        public void ShowMe()
        {
            offsetPosX = 0;
            offsetPosY = 0;
        }

        public void AddCoord(float x, float y)
        {
            Coord newCoord = new Coord();

            if (float.IsNaN(x) || float.IsNaN(y))
                return;

            if (lastPosX != x || lastPosY != y)
            {
                newCoord.x = x;
                newCoord.y = y;
                track.Add(newCoord);

                posX = x;
                posY = y;
            }

            //// draw
            //if (lastPosX != posX || lastPosY != posY || zoomed)
            //{
            //  //gOut.Clear(Color.Black);
            //  gOut.DrawImage(bmp, offsetX, offsetY, bmp.Width, bmp.Height);
            //  zoomed = false;
            //}

            lastPosX = x;
            lastPosY = y;
        }

        /// <summary>
        /// Updates map
        /// </summary>
        public void Update()
        {
            try
            {
                // got gps fix
                /*if (!groundStationPositionFixed && gps.Valid)
                {
                  groundStationPosition.x = gps.X;
                  groundStationPosition.y = gps.Y;
                  groundStationPositionFixed = true;
                  System.Console.WriteLine("map: got gps fix.");
                }

                if (groundStationPositionFixed)
                {
                  posX = groundStationPosition.x + offsetPosX;
                  posY = groundStationPosition.y + offsetPosY;
                }*/

                // finds if position has changed
                for (uint i = 0; i <= maxIdX; i++)
                {
                    if ((posX - 350 * zoom >= minX + (i * partX)) && (posX - 350 * zoom < minX + ((i + 1) * partX)))
                    {
                        if (actX == i)
                        {
                            break;
                        }
                        actX = i;
                        change = true;
                        break;
                    }
                }

                for (uint j = 0; j <= maxIdY; j++)
                {
                    if ((posY - 350 * zoom >= minY + (j * partY)) && (posY - 350 * zoom < minY + ((j + 1) * partY)))
                    {
                        if (actY == j)
                        {
                            break;
                        }
                        actY = j;
                        change = true;
                        break;
                    }
                }

                // redraw map
                if (change || zoomed)
                {
                    System.Console.WriteLine("map: rendering with zoom={0}.", zoom);

                    highway.Clear();
                    railway.Clear();
                    forest.Clear();
                    waterSurface.Clear();
                    stream.Clear();
                    residential.Clear();
                    names.Clear();

                    // load 4 segments of map with zoom
                    LoadSegment(zoom, actX, actY);

                    if (actX + 1 <= maxIdX)
                    {
                        LoadSegment(zoom, actX + 1, actY);
                    }

                    if (actX + 1 <= maxIdX && actY + 1 <= maxIdY)
                    {
                        LoadSegment(zoom, actX + 1, actY + 1);
                    }

                    if (actY + 1 <= maxIdY)
                    {
                        LoadSegment(zoom, actX, actY + 1);
                    }

                    // actual bounds
                    actMinX = (uint)(minX + (actX * partX));
                    actMaxY = (uint)(minY + (actY + 2) * partY);
                    gBmp.Clear(Color.LightGray);

                    // draw map elements
                    for (int i = 0; i < residential.Count; i++)
                    {
                        Point[] pt = new Point[residential[i].refs.Count];
                        for (int j = 0; j < residential[i].refs.Count; j++)
                        {
                            pt[j].X = (int)((residential[i].refs[j].x - actMinX + 350 * zoom) / zoom); ;
                            pt[j].Y = (int)((actMaxY - residential[i].refs[j].y + 350 * zoom) / zoom);
                        }
                        gBmp.FillPolygon(grayBrush, pt);
                    }

                    for (int i = 0; i < forest.Count; i++)
                    {
                        Point[] pt = new Point[forest[i].refs.Count];
                        for (int j = 0; j < forest[i].refs.Count; j++)
                        {
                            pt[j].X = (int)((forest[i].refs[j].x - actMinX + 350 * zoom) / zoom); ;
                            pt[j].Y = (int)((actMaxY - forest[i].refs[j].y + 350 * zoom) / zoom);
                        }
                        gBmp.FillPolygon(greenBrush, pt);
                    }

                    for (int i = 0; i < stream.Count; i++)
                    {
                        Point[] pt = new Point[stream[i].refs.Count];
                        for (int j = 0; j < stream[i].refs.Count; j++)
                        {
                            pt[j].X = (int)((stream[i].refs[j].x - actMinX + 350 * zoom) / zoom);
                            pt[j].Y = (int)((actMaxY - stream[i].refs[j].y + 350 * zoom) / zoom);
                        }
                        gBmp.DrawLines(bluePen, pt);
                    }

                    for (int i = 0; i < waterSurface.Count; i++)
                    {
                        Point[] pt = new Point[waterSurface[i].refs.Count];
                        for (int j = 0; j < waterSurface[i].refs.Count; j++)
                        {
                            pt[j].X = (int)((waterSurface[i].refs[j].x - actMinX + 350 * zoom) / zoom);
                            pt[j].Y = (int)((actMaxY - waterSurface[i].refs[j].y + 350 * zoom) / zoom);
                        }
                        gBmp.FillPolygon(blueBrush, pt);
                    }

                    for (int i = 0; i < railway.Count; i++)
                    {
                        Point[] pt = new Point[railway[i].refs.Count];
                        for (int j = 0; j < railway[i].refs.Count; j++)
                        {
                            pt[j].X = (int)((railway[i].refs[j].x - actMinX + 350 * zoom) / zoom);
                            pt[j].Y = (int)((actMaxY - railway[i].refs[j].y + 350 * zoom) / zoom);
                        }
                        gBmp.DrawLines(grayPen, pt);
                    }

                    for (int i = 0; i < highway.Count; i++)
                    {
                        Point[] pt = new Point[highway[i].refs.Count];
                        for (int j = 0; j < highway[i].refs.Count; j++)
                        {
                            pt[j].X = (int)((highway[i].refs[j].x - actMinX + 350 * zoom) / zoom);
                            pt[j].Y = (int)((actMaxY - highway[i].refs[j].y + 350 * zoom) / zoom);
                        }
                        if (highway[i].type == 0) gBmp.DrawLines(violetPenBold, pt); // dalnice
                        if (highway[i].type == 1 || highway[i].type == 2) gBmp.DrawLines(pinkPenBold, pt); // I.trida
                        if (highway[i].type == 3) gBmp.DrawLines(redPenBold, pt); // II.trida
                        if (highway[i].type == 4) gBmp.DrawLines(yellowPenBold, pt); // III.trida
                        if (highway[i].type == 5) gBmp.DrawLines(whitePen, pt); // ulice
                        if (highway[i].type == 6 || highway[i].type == 7) gBmp.DrawLines(brownPen, pt);  // polni cesta
                    }

                    // draw labels
                    for (int i = 0; i < names.Count; i++)
                    {
                        Point pt = new Point();
                        pt.X = (int)((names[i].coord.x - actMinX + 350 * zoom) / zoom);
                        pt.Y = (int)((actMaxY - names[i].coord.y + 350 * zoom) / zoom);

                        gBmp.DrawString(names[i].text, font, blackBrush, pt);
                    }

                    // draw position
                    Rectangle rect = new Rectangle();
                    rect.X = (int)((groundStationPosition.x - actMinX + 350 * zoom) / zoom);
                    rect.Y = (int)((actMaxY - groundStationPosition.y + 350 * zoom) / zoom);
                    rect.Width = 10;
                    rect.Height = 10;
                    gBmp.FillEllipse(purpleBrush, rect);

                    change = false;
                }
                offsetX = -(int)((posX - actMinX) / zoom);
                offsetY = -(int)((actMaxY - posY) / zoom);

                if (buffer != null)
                {
                    if (track.Count > 1)
                    {
                        Point[] pts = new Point[track.Count];
                        for (int j = 0; j < track.Count; j++)
                        {
                            pts[j].X = (int)((track[j].x - actMinX + 350 * zoom) / zoom);
                            pts[j].Y = (int)((actMaxY - track[j].y + 350 * zoom) / zoom);
                        }
                        gBmp.DrawLines(blackPen, pts);
                    }

                    buffer.Render();

                    gOut.DrawImage(bmp, offsetX, offsetY, bmp.Width, bmp.Height);
                    zoomed = false;
                    /*
                    gOut.DrawLine(blackPen, new Point(340 + (int)crossOffsetX, 350 + (int)crossOffsetY), new Point(360 + (int)crossOffsetX, 350 + (int)crossOffsetY));
                    gOut.DrawLine(blackPen, new Point(350 + (int)crossOffsetX, 340 + (int)crossOffsetY), new Point(350 + (int)crossOffsetX, 360 + (int)crossOffsetY));
                

                    gOut.DrawLine(blackPen, new Point(340 - (int)actMinX, 350), new Point(360 - (int)actMinX, 350));
                    gOut.DrawLine(blackPen, new Point(350 - (int)actMinX, 340), new Point(350 - (int)actMinX, 360));

                    gOut.DrawLine(blackPen, new Point(340, 350), new Point(360, 350));
                    gOut.DrawLine(blackPen, new Point(350, 340), new Point(350, 360));

                    /*
                    gOut.DrawLine(blackPen, new Point(340 + offsetX, 350 + offsetY),
                        new Point(360 + offsetX, 350 + offsetY));
                    gOut.DrawLine(blackPen, new Point(350 + offsetX, 340 + offsetY),
                        new Point(350 + offsetX, 360 + offsetY));
                    */
                    /*
                    gOut.DrawLine(blackPen, new Point(340 + (int)offsetPosX, 350 + (int)offsetPosY),
                        new Point(360 + (int)offsetPosX, 350 + (int)offsetPosY));
                    gOut.DrawLine(blackPen, new Point(350 + (int)offsetPosX, 340 + (int)offsetPosY),
                        new Point(350 + (int)offsetPosX, 360 + (int)offsetPosY));
                    //*/
                }


                lastPosX = posX;
                lastPosY = posY;
            }
            catch(Exception e)
            {
                System.Console.WriteLine(e.Message);
            }
        }
        
    }
}